Спринт 1/18 → Тема 5/13: Python «под капотом» → Урок 1/1
Переменные и память
Переменные другими словами
О переменных в Python вам уже известно многое, вы с ними работали много раз.
- Переменная работает как подписанная коробка или помеченная ячейка, куда можно что-то положить и не потерять:
PYTHON
- Первое появление переменной в коде называется объявлением переменной.
- При объявлении переменной указывается:
- имя переменной,
- знак присваивания
=, - присваиваемое значение.
- В ходе выполнения программы значение присвоенное переменной может меняться.
- Имя переменной придумывает сам программист.
Память и переработка мусора
Есть смысл заглянуть за кулисы и посмотреть, что происходит с переменными в памяти устройства.
Начнём с простого примера:
PYTHON
После того, как строка напечатана, объект строки не будет использован повторно: к нему невозможно обратиться для повторного использования, и Python это отлично понимает.
Как только объекты становятся не нужны программе — они становятся «мусором» и автоматически удаляются сборщиком мусора. В Python есть встроенный неотключаемый механизм для сборки мусора — алгоритм подсчёта ссылок.
Когда для переменной указывается значение, происходит следующее: в памяти устройства выделяется область, в которую помещается указанное значение, а сама переменная при этом только ссылается на эту ячейку. Сборщик мусора не удаляет такие данные: он видит, что на ячейку памяти есть ссылка и понимает, что эти данные нужны программе.
PYTHON
Переменная указывает на адрес объекта в памяти, в котором хранятся данные. Адрес блока памяти — это уникальный числовой идентификатор. К этому идентификатору можно обратиться через встроенную функцию
id().PYTHON
При выполнении одного и того же кода на разных компьютерах (и даже на одном компьютере, после перезапуска Python) значение ID ячейки памяти для одного и того же объекта будет разным.
На один блок памяти могут ссылаться сколько угодно переменных. Например:
PYTHON
Если значение переменной переопределяют — в памяти создаётся новый объект.
PYTHON
Что случилось:
- для объекта с новым значением выделена новая ячейка памяти,
- переменная
brand_nameтеперь ссылается на новый объект, - объект с прежним значением
'Unicorn'будет уничтожен сборщиком мусора.
Сборщик думает так: «на эту ячейку памяти не ссылается ни одна переменная; значит, обратиться к этому значению из программы невозможно; удаляем этот хлам из памяти».
Сборщик мусора не станет освобождать ячейку памяти, если на эту ячейку есть хотя бы одна ссылка.
Изменяемые и неизменяемые типы данных
В Python все типы объектов делятся на изменяемые и неизменяемые. Такой вариант русского перевода может трактоваться неоднозначно. Более подходящим был бы перевод «мутирующие и немутирующие» типы: это лучше отражает способность объекта изменять своё содержимое.
Изменяемые типы
Значения изменяемых (англ. mutable) типов данных хранятся в памяти так, что содержимое ячейки памяти можно изменять. При изменениях переменная будет ссылаться всё на ту же ячейку памяти.
PYTHON
К изменяемым типам относятся: множества (class
set), списки (class list) и словари (class dict).Неизменяемые типы
Значения неизменяемых (англ. inmutable) типов хранятся в памяти так, что изменить данные в ячейке памяти невозможно.
При попытке изменить значение объекта в памяти вернётся ошибка. При переопределении переменной неизменяемого типа будет создан новый объект в памяти.
PYTHON
Из встроенных типов данных к неизменяемым относятся числа (
int, float, complex), строки (class str), кортежи (tuple).Если какой-то переменной присваивается значение, уже ранее присвоенное другой переменной, то обе эти переменные будут ссылаться на одну ячейку памяти. Это справедливо только для неизменяемых типов данных.
PYTHON
Хеширование
Не все неизменяемые объекты одинаково неизменяемы. Например, элементом кортежа (это неизменяемый тип) может быть список (а список — это изменяемый тип данных!):
PYTHON
Сам кортеж будет по-прежнему неизменяемым. Попытка изменить один из элементов кортежа вызовет ошибку:
PYTHON
Но при этом во вложенный список можно добавить новые элементы — и ошибки не будет:
PYTHON
Получается, что элементы неизменяемого объекта могут быть изменяемыми.
Элементами множества (set) или ключами словаря (dict) могут быть только полностью неизменные объекты — неизменяемым должен быть сам объект, его элементы и все вложенные в них объекты и элементы.
Связано это с тем, что ключи словаря и элементы множества должны быть хешируемыми.
Хешированием называют преобразование данных в строку фиксированной длины, называемую «хешем». Как правило хеш записывается в виде шестнадцатиричного числа.
Неважно, какой длины данные подаются на вход — хоть одно слово, хоть роман «Война и мир» — на выходе всегда будет хеш одинаковой длины. Например, из строки
'Unicorn' и числа 42 можно получить два хеша: 393eb74047bb90c8d80dea54218430ee и a1d0c6e83f027327d8461063f4ac58a6. Хеш такой же длины получится и из романа «Война и мир», только вычисляться он будет значительно дольше. На примере «Войны и мира» довольно очевидно, что хеширование может работать «только в одну сторону»: можно преобразовать данные в хеш, но нельзя восстановить данные из хеша.Хеши не всегда получаются уникальными: один и тот же алгоритм хеширования для разных данных может вернуть одинаковые хеши. Такая ситуация называется коллизией. Впрочем, такие случаи относительно редки, и для решения коллизий существуют готовые рецепты.
Хеш напрямую зависит от содержания данных, и повторное хеширование данных должно давать тот же результат. Для корректной работы словарей и множеств Python необходимо, чтобы ключи словарей и элементы множеств давали всегда один и тот же хеш. Это невозможно, если объект Python не будет полностью состоять из неизменяемых элементов.
Объекты, которые могут меняться или в составе которых есть изменяемые элементы, не хешируются. Следовательно, они не могут быть ключами словаря или элементами множеств. Использование таких объектов в словарях или во множествах приведёт к ошибке
TypeError — «ошибка типа данных»:PYTHON
При попытке создать множество из кортежа, внутри которого есть изменяемый элемент, Python обнаружил внутри кортежа нехешируемый тип данных: «список» (
unhashable type: list) и выбросил соответствующую ошибку.